/*
 * @Author: your name
 * @Date: 2020-07-21 18:51:53
 * @LastEditTime: 2020-07-21 18:52:26
 * @LastEditors: Please set LastEditors
 * @Description: 手写promsie源码
 * @FilePath: /fed-e-task-01-01/code/代码题/prictice4-promise/MyPromise.js
 */ 


const PENDING = 'pending'  //等待中
const FULFILLED=  'fulfilled' //成功
const REJECTED = 'rejected'  //失败

class MyPromise {
 constructor(executor) {
   try {
     executor(this.resolve, this.reject) //立即执行的执行器
   }catch(e) {
     this.reject(e)
   }
 }

   status = PENDING  //初始promsie状态, 默认为pedding
   value = undefined;  //resolve回来的值
   reason = undefined; //reject回来的值
   /**
    * successCallback && failCallback为数组的原因，当多次调用.then（不是链式调用）的时候，需要将每一个回调函数存储起来，再通过shift取出第一个依次执行
    */
   successCallback = [] //成功的回调
   failCallback = [] //失败的回调

   //成功的回调函数
    resolve = value => {
     //  这里是 !== ,因为status不是等待状态的话，要终止函数继续进行运行，是等待状态的话，if语句不执行，就可以执行下面的代码了
      if(this.status !== PENDING) return;
      //成功的回调函数
      this.status = FULFILLED
      this.value = value
      //判断成功回调是否存在，如果存在，则调用
     //  this.successCallback && this.successCallback(value)
     // 
     while(this.successCallback.length) this.successCallback.shift()()
      
    }
    reject = reason => {

     if(this.status !== PENDING) return;
     //失败的回调函数
     this.status = REJECTED

      this.reason = reason
      //判断失败回调是否存在，如果存在，则调用
     //  this.failCallback && this.failCallback(reason)
     while(this.failCallback.length) this.failCallback.shift()()
    }

    then(successCallback, failCallback) {

    //当调用then时，then里面不做任何处理（也就是不传递任务参数），他会一层层传递,下面两个三目运算符的返回值就相当于then的两个参数
   
    successCallback = successCallback ? successCallback : value => value
    failCallback = failCallback ? failCallback : reason => {throw reason}

     /**
      *   此promise也需要一个执行器，需要立即执行，所以可以把if判断放入里面，反正都是立即执行的
      *   
      */
      let promise2 = new MyPromise((resolve, reject) => {
         if(this.status === FULFILLED) { 
          
             //这里使用定时器原因，promise2没执行完无法进行传参，让这一步延迟执行，以便拿到promise2
             setTimeout(() => {
             try {
               //因为下一个then接收的也是resolve(‘xxx')出去的，所以用一个变量接收
               let x = successCallback(this.value)
               /**
                *  判断x的值是普通值还是promise对象
                *  如果是普通值直接返回x，
                *  如果是promise对象，查看promise对象返回的结果
                *  再根据promise对象返回的结果，决定调用resolve还是reject
                *  
                */
               resolvPromise(promise2, x, resolve, reject)
             } catch (error) {
               reject(error)
             }
             }, 0)
           


         }else if(this.status === REJECTED) {
           //这里使用定时器原因，promise2没执行完无法进行传参，让这一步延迟执行，以便拿到promise2
           setTimeout(() => {
               try {
                 console.log(failCallback,'failCallback');
               //因为下一个then接收的也是resolve(‘xxx')出去的，所以用一个变量接收
               let x = failCallback(this.reason)
               /**
                *  判断x的值是普通值还是promise对象
                *  如果是普通值直接返回x，
                *  如果是promise对象，查看promise对象返回的结果
                *  再根据promise对象返回的结果，决定调用resolve还是reject
                *  
                */
               resolvPromise(promise2, x, resolve, reject)
             } catch (error) {
               reject(error)
             }
             }, 0)
         }else {
             /**
              * 当进入pending状态时，将两个回调函数存储起来，这里是为异步做考虑
              * 如果直接将结果push到数组中，是无法try catch所以直接push一个函数
              */
             // this.successCallback.push(successCallback)
             // this.failCallback.push(failCallback)
             this.successCallback.push(() => {
               // successCallback(this.value)
               //这里使用定时器原因，promise2没执行完无法进行传参，让这一步延迟执行，以便拿到promise2
               setTimeout(() => {
                   try {
                   //因为下一个then接收的也是resolve(‘xxx')出去的，所以用一个变量接收
                   let x = successCallback(this.value)
       
                   /**
                    *  判断x的值是普通值还是promise对象
                    *  如果是普通值直接返回x，
                    *  如果是promise对象，查看promise对象返回的结果
                    *  再根据promise对象返回的结果，决定调用resolve还是reject
                    *  
                    */
                   resolvPromise(promise2, x, resolve, reject)
                     } catch (error) {
                       reject(error)
                     }
                 }, 0)
             })
             this.failCallback.push(() => {
               // failCallback(this.reason)
               //这里使用定时器原因，promise2没执行完无法进行传参，让这一步延迟执行，以便拿到promise2
               setTimeout(() => {
                   try {
                   //因为下一个then接收的也是resolve(‘xxx')出去的，所以用一个变量接收
                   let x = failCallback(this.reason)
                   /**
                    *  判断x的值是普通值还是promise对象
                    *  如果是普通值直接返回x，
                    *  如果是promise对象，查看promise对象返回的结果
                    *  再根据promise对象返回的结果，决定调用resolve还是reject
                    *  
                    */
                   resolvPromise(promise2, x, resolve, reject)
                   } catch (error) {
                     reject(error)
                   }
                 }, 0)
             })
          }
      })
     //返回一个promise，让xiayigethen进行接收
      return promise2


    }
    
    /**
     * @param {*} failCallback 失败方法
     */
    catch(failCallback) {
      //第一个参数undefined，反正也是失败，不需要成功，catch后面还可以链式调用其他方法，所以加上return返回出去
      return this.then(undefined, failCallback)
    }
   

    /**
     * finally: 定义在MyPromise类的原型对象上
     * 特点： 1.无论当前promsie对象成功还是失败，都会执行一次，
     *       2.可以链式调用hen方法来拿到当前promise对象的返回值
     * 接收一个回调函数作为参数，无论成功失败，都要调用函数,then方法知道返回状态，所以调用.then
     * 在then的两个参数都调用callback，这样无论promsie对象成功或者失败，finally里面的回调函数都能够执行，
     * finally需要链式调用then方法，返回promsie对象，其中this.then就是返回promsie对象的，只需return就够了
     * 
     */
    finally(callback) {
      return this.then((value) => {
        //错误：此方法只适合同步，如果return一个异步，便不会等待异步执行完成再执行
       //  callback()
       //  return value
       //正确： 将callback()传递给promise.resolve(),使用返回一个promsie对象，在其then里面返回参数,然后将最终的结果return出去
       return MyPromise.resolve(callback()).then(() => value)
      }, (reason) => {
       //  callback()
       //  throw reason
       return MyPromise.resolve(callback()).then(() => {throw reason})
      })
    }

   /**
    * all是promise的静态方法，只能promise以及子类调用，接收一个数组
    * all方法返回一个promise对象，
    * 
    */
    static all(array) {
     return new MyPromise((resolve, reject) => {
       let result = []  //结果数组
       /**
        * 定义变量目的，for循环同步的，然后promsie里面可能有setTimeout,导致同时输出时导致array里面部分没有执行完
        * 当 index === array.length时，表示array全部执行完毕，此时再resolve(result)
        */
       let index = 0;  
       for(let i = 0; i < array.length; i++) {
         function addData(key,value) {
           result[key] = value
           index ++
           if(index === array.length) {
             resolve(result)
           }
         } 
         let current = array[i]
         if(current instanceof MyPromise) { 
           //判断current是否是MyPromise的实例，如果是则是promise对象，先执行promsie，再把结果放到数组中
           current.then(value => {addData(i, value)}, reason => {reject(reason)})
         }else {
           //如果是普通，直接返回到数组中
           addData(i, current)
         }
       }
     })
    }

   /**
    * 如果值是promsie对象，则直接返回，
    * 如果不是，则创建一个promise对象再返回
    */

   static resolve(current) {
     if(current instanceof MyPromise) return current
     return new MyPromise(resolve => resolve(current))
    }
}


function resolvPromise(promise2, x, resolve, reject) {
/**
*   当两者相等，所以那边调用的promise return 的是自己， 类似这个注释代码，此时要进行判断并抛出错误
*    let promise = new Promise((resolve, reject) => {
        resolve('100')
     })
   let p1 =  promise.then(res => {
        console.log(res);
        return p1
    })
    p1.then(() => {}, error => {console.log(error);})
*/
 if(promise2 === x) {
   //报的是类型错误，使用TypeError，用return原因，报错之后不往下执行
    return reject(new TypeError('TypeError: Chaining cycle detected for promise #<Promise>'))
 }
 //如果x是MyPromise的实例，就是一个promise对象，
 if(x instanceof MyPromise) {
   //调用promise对象的then方法查看状态,这两个方法相当于successCallback和failCallback，所以也可以简写，直接调用resolve，和reject
   // x.then(value => resolve(value), reason => reject(reason))
   x.then(resolve, reject)
 }else {
   resolve(x)
 }
}


module.exports = MyPromise
